using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;

namespace v6502
{
    class InstructionSet
    {
        private Hashtable opcodes = new Hashtable();

        private string HexNumbers = "0123456789ABCDEF";
        private string Numbers = "0123456789";

        public InstructionSet()
        {
            initAddressingModes();
            initInstructionLength();

            //                              ACC,  IMP,  IMM,  ZP,   ZPX,  ZPY,  ABS,  ABX,  ABY,  IND,  INX,  INY,  BXX
            opcodes.Add("ASL", new byte[] { 0x0A, 0x00, 0x00, 0x06, 0x16, 0x00, 0x0e, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("BIT", new byte[] { 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("BPL", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 });
            opcodes.Add("BMI", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30 });
            opcodes.Add("BVC", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50 });
            opcodes.Add("BVS", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70 });
            opcodes.Add("BCC", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90 });
            opcodes.Add("BCS", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0 });
            opcodes.Add("BNE", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0 });
            opcodes.Add("BEQ", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0 });
            opcodes.Add("CMP", new byte[] { 0x00, 0x00, 0xc9, 0xc5, 0xd5, 0x00, 0xcd, 0xdd, 0xd9, 0x00, 0xc1, 0xd1, 0x00 });
            opcodes.Add("CPX", new byte[] { 0x00, 0x00, 0xe0, 0xe4, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("DEC", new byte[] { 0x00, 0x00, 0x00, 0xc6, 0xd6, 0x00, 0xce, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("EOR", new byte[] { 0x00, 0x00, 0x49, 0x45, 0x55, 0x00, 0x4d, 0x5d, 0x59, 0x00, 0x41, 0x51, 0x00 });
            opcodes.Add("SEC", new byte[] { 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("CLI", new byte[] { 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("SEI", new byte[] { 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("CLV", new byte[] { 0x00, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("CLD", new byte[] { 0x00, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("SED", new byte[] { 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("LSR", new byte[] { 0x4A, 0x00, 0x00, 0x46, 0x56, 0x00, 0x4e, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("ORA", new byte[] { 0x00, 0x00, 0x09, 0x05, 0x15, 0x00, 0x0d, 0x1d, 0x19, 0x00, 0x01, 0x11, 0x00 });
            opcodes.Add("BRK", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("TAX", new byte[] { 0x00, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("TXA", new byte[] { 0x00, 0x8A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("DEX", new byte[] { 0x00, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("INX", new byte[] { 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("TAY", new byte[] { 0x00, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("TYA", new byte[] { 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("DEY", new byte[] { 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("INY", new byte[] { 0x00, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("ROR", new byte[] { 0x6A, 0x00, 0x00, 0x66, 0x76, 0x00, 0x6e, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("ROL", new byte[] { 0x2A, 0x00, 0x00, 0x26, 0x36, 0x00, 0x2e, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("RTI", new byte[] { 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("RTS", new byte[] { 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("SBC", new byte[] { 0x00, 0x00, 0xe9, 0xe5, 0xf5, 0x00, 0xed, 0xfd, 0xf9, 0x00, 0xe1, 0xf1, 0x00 });
            //opcodes.Add("BRK", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("TXS", new byte[] { 0x00, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("TSX", new byte[] { 0x00, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("PHA", new byte[] { 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("PLA", new byte[] { 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("PHP", new byte[] { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("PLP", new byte[] { 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("ADC", new byte[] { 0x00, 0x00, 0x69, 0x65, 0x75, 0x00, 0x6D, 0x7D, 0x79, 0x00, 0x61, 0x71, 0x00 });
            opcodes.Add("LDA", new byte[] { 0x00, 0x00, 0xA9, 0xA5, 0xB5, 0x00, 0xAD, 0xBD, 0xB9, 0x00, 0xA1, 0xB1, 0x00 });
            opcodes.Add("STA", new byte[] { 0x00, 0x00, 0x00, 0x85, 0x95, 0x00, 0x8D, 0x9D, 0x99, 0x00, 0x81, 0x91, 0x00 });
            opcodes.Add("AND", new byte[] { 0x00, 0x00, 0x29, 0x25, 0x35, 0x31, 0x2D, 0x3D, 0x39, 0x00, 0x21, 0x31, 0x00 });
            opcodes.Add("CLC", new byte[] { 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("LDY", new byte[] { 0x00, 0x00, 0xa0, 0xA4, 0xB4, 0x00, 0xAC, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("LDX", new byte[] { 0x00, 0x00, 0xA2, 0xA6, 0x00, 0xB6, 0xAE, 0x00, 0xBE, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("STX", new byte[] { 0x00, 0x00, 0x00, 0x86, 0x00, 0x96, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("STY", new byte[] { 0x00, 0x00, 0x00, 0x84, 0x94, 0x00, 0x8C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("INC", new byte[] { 0x00, 0x00, 0x00, 0xE6, 0xF6, 0x00, 0xEE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("CPY", new byte[] { 0x00, 0x00, 0xC0, 0xC4, 0x00, 0x00, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("JMP", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00 });
            opcodes.Add("JSR", new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
            opcodes.Add("NOP", new byte[] { 0x00, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
        }

        private Hashtable labels = new Hashtable();

        private void addLabel(string label, byte[] address)
        {
            label = label.ToUpper();
            if(labels.ContainsKey(label))
            {
                if (labels[label].Equals(new byte[] { 0xFF, 0xFF }))
                {
                    labels[label] = address;
                }
            }
            else
                labels.Add(label, address);
        }

        private byte[] getLabelAddress(string label)
        {
            if (labels.ContainsKey(label))
                return (byte[])labels[label];
            else
            {
                addLabel(label, new byte[] { 0xFF, 0xFF });
                return (byte[])labels[label];
            }
        }

        public string labelCheck(string ins)
        {
            string cleanLine = ins;
            ins = ins.TrimStart(' ', '\t');
            ins = ins.Replace('\t', ' ');
            ins = ins.Replace(" ", "");

            if (ins.Contains(":"))
            {
                string[] insSplit = ins.Split(':');
                //Console.WriteLine(insSplit[0]);
                //addLabel(insSplit[0], BitConverter.GetBytes(currentAddress));
                cleanLine = insSplit[1];
                if(cleanLine.Length > 0)
                    cleanLine = cleanLine.Insert(3, " ");
                
                addLabel(insSplit[0], BitConverter.GetBytes(currentAddress));
            }

            if (cleanLine.Length > 0)
            {
                string[] tmp = cleanLine.Split(' ');
                string instruction = tmp[0].ToUpper();
                string op = "";
                if (tmp.Length > 1)
                    op = tmp[1].ToUpper();
                string adm = "";
                adm = getAddressingMode(op);
                currentAddress += Convert.ToInt16(instructionLength[adm]);
            }

            return cleanLine;
        }

        private Hashtable instructionLength = new Hashtable();

        private void initInstructionLength()
        {
            instructionLength.Add("ACC", 1);
            instructionLength.Add("IMP", 1);
            instructionLength.Add("IMM", 2);
            instructionLength.Add("ZP",  2);
            instructionLength.Add("ZPX", 2);
            instructionLength.Add("ZPY", 2);
            instructionLength.Add("ABS", 3);
            instructionLength.Add("ABX", 3);
            instructionLength.Add("ABY", 3);
            instructionLength.Add("IND", 3);
            instructionLength.Add("INX", 2);
            instructionLength.Add("INY", 2);
            instructionLength.Add("BXX", 2);
        }

        private Hashtable AddressingModes = new Hashtable();

        private void initAddressingModes()
        {
            AddressingModes.Add("ACC", 0);
            AddressingModes.Add("IMP", 1);
            AddressingModes.Add("IMM", 2);
            AddressingModes.Add("ZP",  3);
            AddressingModes.Add("ZPX", 4);
            AddressingModes.Add("ZPY", 5);
            AddressingModes.Add("ABS", 6);
            AddressingModes.Add("ABX", 7);
            AddressingModes.Add("ABY", 8);
            AddressingModes.Add("IND", 9);
            AddressingModes.Add("INX", 10);
            AddressingModes.Add("INY", 11);
            AddressingModes.Add("BXX", 12);
        }

        private string getAddressingMode(string operand)
        {
            if (operand.Equals("A"))
                return "ACC"; // Accumulator

            if (operand.Equals(""))
                return "IMP"; // Implied

            if (operand.StartsWith("#"))
                return "IMM"; // Immediate

            if (operand.EndsWith(",X"))
                if (operand.Substring(1, operand.Length - 2).Length <= 2)
                    return "ZPX"; // Zero Page X
                else
                    return "ABX"; // Absolute X

            if (operand.EndsWith(",X)"))
                return "INX"; // Indirect X

            if (operand.EndsWith("),Y"))
                return "INY"; // Indirect Y

            if (operand.EndsWith(",Y"))
                if (operand.Substring(1, operand.Length - 2).Length <= 2)
                    return "ZPY"; // Zero Page X
                else
                    return "ABY"; // Absolute Y

            if (operand.Length <= 3)
                return "ZP"; // Zero Page

            if (operand.Length > 3)
                return "ABS"; // Absolute

            if (operand.EndsWith(")"))
                return "IND"; // Indirect

            return "LBL";
        }

        private byte lookupOpcode(string instruction, string addressingMode)
        {
            string adMode = addressingMode;
            //string[] iSplit = instruction.Split(' ');
            string ins = instruction; //iSplit[0];
            byte[] opcodeList;
            byte oc;

            if (ins.ToUpper().StartsWith("B"))
            {
                if (!(ins.ToUpper().Equals("BIT")) || !(ins.ToUpper().Equals("BRK")))
                    adMode = "BXX";
            }

            opcodeList = (byte[])opcodes[ins.ToUpper()];

            if(adMode.Equals("LBL"))
            {
                if (instruction.EndsWith(")"))
                    adMode = "IND";
                else
                    adMode = "ABS";
            }
            
            oc = opcodeList[(int)AddressingModes[adMode]];


            //Console.Write(
            //    String.Format("{0:x2}", (uint)System.Convert.ToUInt32(oc.ToString()))
            //    );

            return oc;
        }

        private byte[] getValue(string op)
        {
            string tmp = "";
            byte[] data;
            for (int i = 0; i < op.Length; i++)
            {
                if (
                    (op.StartsWith("$"))
                    //|| (op.StartsWith("("))
                    || (op.StartsWith("($"))
                    || (op.StartsWith("#"))
                    || (op.StartsWith("#$"))
                    )
                    if (HexNumbers.Contains(op[i].ToString()))
                    {
                        tmp += op[i].ToString();
                    }
            }

            if (tmp.Length % 2 > 0)
                tmp=tmp.Insert(0, "0");

            if (tmp.Length == 2)
            {
                data = new byte[1];
                data[0] = (byte)Convert.ToByte(tmp, 16);
                //data[1] = (byte)Convert.ToByte(tmp[1].ToString(), 16);
            }
            else if (tmp.Length == 4)
            {
                data = new byte[2];
                data[1] = (byte)Convert.ToUInt16(tmp.Substring(0, 2), 16);
                data[0] = (byte)Convert.ToUInt16(tmp.Substring(2, 2), 16);
                //data[2] = (byte)Convert.ToUInt16(tmp[0].ToString(), 16);
                //data[3] = (byte)Convert.ToUInt16(tmp[1].ToString(), 16);
            }
            else if((tmp.Length == 0) && (op.Length > 0))
            {
                op = op.Replace(" ", "");
                op = op.Replace("$", "");
                op = op.Replace("#", "");
                op = op.Replace(",X", "");
                op = op.Replace(",Y", "");
                op = op.Replace("(", "");
                op = op.Replace(")", "");

                data = new byte[2];
                data = getLabelAddress(op);
            }
            else
                data = new byte[0];

            //Console.WriteLine(data);
            return data;
        }

        private short currentAddress = 0x600;

        public byte[] ASMtoMC(string asminstructions)
        {
            ArrayList machinecode = new ArrayList();
            string[] asmline = asminstructions.Split('\n');

            currentAddress = 0x600;

            for (int i = 0; i < asmline.Length; i++)
            {
                asmline[i] = labelCheck(asmline[i]);
            }
            
            currentAddress = 0x600;

            for (int i = 0; i < asmline.Length; i++)
            {
                //byte[] machinecode;
                //ArrayList machinecode;
                if (asmline[i].Length > 1)
                {
                    string adm = "";
                    string line = asmline[i];
                    //line = labelCheck(line);
                    string[] tmp = line.Split(' ');
                    string instruction = tmp[0].ToUpper();
                    string op = "";
                    if (tmp.Length > 1)
                        op = tmp[1].ToUpper();
                    byte opcode;
                    byte[] operand;

                    adm = getAddressingMode(op);
                    opcode = lookupOpcode(instruction, adm);
                    operand = getValue(op);
                    if (operand.Length == 2)
                    {
                        if (instruction.ToUpper().StartsWith("B"))
                        {
                            if (!(instruction.ToUpper().Equals("BIT")) || !(instruction.ToUpper().Equals("BRK")))
                            {
                                machinecode.Add(opcode);
                                currentAddress++;

                                int st = BitConverter.ToInt16(operand, 0);
                                //st = currentAddress - st;
                                // oper=oper-((pc+2)&0xffff);
                                st = st - ((currentAddress + 2) & 0xffff);
                                machinecode.Add((byte)st);
                                currentAddress++;
                            }
                            else
                            {
                                // same as below....
                                machinecode.Add(opcode);
                                currentAddress++;
                                machinecode.Add(operand[0]);
                                currentAddress++;
                                machinecode.Add(operand[1]);
                                currentAddress++;
                            }
                        }
                        else
                        {
                            /*machinecode = new byte[3];
                            machinecode[0] = opcode;
                            machinecode[1] = operand[0];
                            machinecode[2] = operand[1];*/
                            machinecode.Add(opcode);
                            currentAddress++;
                            machinecode.Add(operand[0]);
                            currentAddress++;
                            machinecode.Add(operand[1]);
                            currentAddress++;
                        }
                    }
                    else if (operand.Length == 1)
                    {
                        /*machinecode = new byte[2];
                        machinecode[0] = opcode;
                        machinecode[1] = operand[0];*/
                        machinecode.Add(opcode);
                        currentAddress++;
                        machinecode.Add(operand[0]);
                        currentAddress++;
                    }
                    else
                    {
                        /*machinecode = new byte[1];
                        machinecode[0] = opcode;*/
                        machinecode.Add(opcode);
                        currentAddress++;
                    }
                }
            }

            return (byte[])machinecode.ToArray(typeof(byte));
        }
    }
}
